﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AgileRollover.Utility;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Server;
using System.Data.SqlClient;
using System.Text.RegularExpressions;

namespace AgileRollover
{
    public class TfsTeam
    {
        //req wits, task wits, finished state, prev iteration, current iteration, next iteration
        //area paths

        private string m_id;
        private string m_name;
        private string m_description;
        private string m_projectId;
        private string m_projectName;
        private string m_collection;
        private Dictionary<string, Boolean> m_areaPaths;
        private Dictionary<string, Dictionary<string, object>> m_members;
        private Dictionary<string, string> m_properties;
        private List<string> m_reqWits;
        private List<string> m_taskWits;
        private string m_reqFinishedState;
        private string m_taskFinishedState;
        private string m_currentIteration;
        private string m_previousIteration;

        public string Id
        {
            get { return m_id; }
            set { m_id = value; }
        }

        public string ProjectId
        {
            get { return m_projectId; }
            set { m_projectId = value; }
        }

        public string ProjectName
        {
            get { return m_projectName; }
            set { m_projectName = value; }
        }

        public string Collection
        {
            get { return m_collection; }
            set { m_collection = value; }
        }

        public Dictionary<string, Boolean> AreaPaths
        {
            get { return m_areaPaths; }
            set { m_areaPaths = value; }
        }

        public Dictionary<string, Dictionary<string, object>> Members
        {
            get { return m_members; }
            set { m_members = value; }
        }

        public Dictionary<string, string> Properties
        {
            get { return m_properties; }
            set { m_properties = value; }
        }

        public List<string> ReqWits
        {
            get { return m_reqWits; }
            set { m_reqWits = value; }
        }

        public List<string> TaskWits
        {
            get { return m_taskWits; }
            set { m_taskWits = value; }
        }

        public string ReqFinishedState
        {
            get { return m_reqFinishedState; }
            set { m_reqFinishedState = value; }
        }

        public string TaskFinishedState
        {
            get { return m_taskFinishedState; }
            set { m_taskFinishedState = value; }
        }

        public string CurrentIteration
        {
            get { return m_currentIteration; }
            set { m_currentIteration = value; }
        }

        public string PreviousIteration
        {
            get { return m_previousIteration; }
            set { m_previousIteration = value; }
        }
        public string Name
        {
            get { return m_name; }
            set { m_name = value; }
        }

        public string Description
        {
            get { return m_description; }
            set { m_description = value; }
        }



        public TfsTeam()
        {
            m_reqWits = new List<string>();
            m_taskWits = new List<string>();
            m_areaPaths = new Dictionary<string, Boolean>();
            m_properties = new Dictionary<string, string>();
            m_members = new Dictionary<string, Dictionary<string, object>>();
        }

        public void InitializeFromTFS(string username, string password, string collection, string teamId)
        {
            
            TfsJSON tfsReader = new TfsJSON(collection);
            if (username != null && password != null)
                tfsReader = new TfsJSON(collection, username, password);

            using (var tpc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(collection)))
            {
                IRegistration regService = (IRegistration)tpc.GetService(typeof(IRegistration));
                ICommonStructureService structService = (ICommonStructureService)tpc.GetService(typeof(ICommonStructureService));
                string[] splitout = regService.GetRegistrationEntries("WorkItemTracking")[0].Databases[0].ConnectionString.Split(';');
                string dbConnection = splitout[0] + ";" + splitout[1] + "; Integrated Security=SSPI;";

                TfsTeamService teamService = new TfsTeamService();
                teamService.Initialize(tpc);

                TeamFoundationTeam teamObject = teamService.ReadTeam(Guid.Parse(teamId), null);
                                
                m_name = teamObject.Name;
                m_id = teamObject.Identity.TeamFoundationId.ToString();
                m_collection = collection;
                Uri foo = new Uri(teamObject.Project);
                m_projectId = foo.Segments[foo.Segments.Length - 1];
                m_projectName = structService.GetProject(teamObject.Project).Name;
                m_description = teamObject.Description;

                Console.WriteLine("Loading information for " + m_name);
                InitPropertiesFromTFS(teamObject);
                InitMembersFromTFS(tpc, teamObject);
                InitIterationsFromTFS(tfsReader);
                if (m_currentIteration != null && m_previousIteration != null)
                {
                    InitWitsFromTFS(tfsReader, dbConnection);
                    InitAreaPathsfromDB(dbConnection);
                    Console.WriteLine("Completed reading " + m_name);
                }
                else
                {
                    Console.WriteLine("Team has no Agile settings, skipping.");
                }
            }
        }

        private void InitPropertiesFromTFS(TeamFoundationTeam team)
        {
            foreach (var property in team.GetProperties())
            {
                m_properties.Add(property.Key, property.Value.ToString());
            }
        }

        private void InitMembersFromTFS(TfsTeamProjectCollection tpc, TeamFoundationTeam team)
        {
            TeamFoundationIdentity[] teamMembers = team.GetMembers(tpc, MembershipQuery.Direct);
            foreach (var member in teamMembers)
            {
                m_members.Add(member.TeamFoundationId.ToString(), member.GetProperties().ToDictionary(x => x.Key, x => x.Value));
            }
        }

        public void ImportTeam(TeamFoundationTeam teamObject, TeamFoundationIdentity[] teamMembers)
        {
            using (var tpc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(m_collection)))
            {
                ICommonStructureService structService = (ICommonStructureService)tpc.GetService(typeof(ICommonStructureService));
                IIdentityManagementService identService = (IIdentityManagementService)tpc.GetService(typeof(IIdentityManagementService));
                ProjectInfo teamProject = structService.GetProjectFromName(m_projectName);
                m_projectName = teamProject.Name;

                TfsTeamService teamService = new TfsTeamService();
                teamService.Initialize(tpc);
                //IDictionary<string, object> props = (IDictionary<string,object>)teamObject.GetProperties();
                
                try
                {
                    TeamFoundationTeam newTeam = teamService.CreateTeam(m_projectId, teamObject.Name, teamObject.Description, (IDictionary<string, object>)teamObject.GetProperties());
                    foreach( TeamFoundationIdentity member in teamMembers)
                    {
                        identService.AddMemberToApplicationGroup(newTeam.Identity.Descriptor, member.Descriptor);
                    }
                    //m_id = newTeam.Identity.TeamFoundationId.ToString();
                }
                catch (Exception e) 
                {
                    Console.WriteLine(e);
                }              
            }
        }

        public void InitIterationsFromTFS(TfsJSON tfsReader)
        {
            var jsonPayload = tfsReader.TfsTeamSettings(m_projectName, m_name);
            long ticks=0;
            long tickComp=0;
            var root = (Dictionary<string, object>)jsonPayload;
            if (root == null || root.Count == 0)
            {
                Console.WriteLine("No Iterations are set for this team");
                return;
            }

            m_currentIteration = ((Dictionary<string, object>)root["currentIteration"])["friendlyPath"].ToString();
            foreach (Dictionary<string, object> iteration in (object[])root["previousIterations"])
            {
                if (iteration["finishDate"] != null)
                {
                    tickComp = DateTime.Now.Ticks - ((DateTime)iteration["finishDate"]).Ticks;
                    if (ticks == 0)
                    {
                        ticks = tickComp;
                        m_previousIteration = (string)iteration["friendlyPath"];
                    }
                    else
                    {
                        if (tickComp < ticks)
                            ticks = tickComp;
                        m_previousIteration = (string)iteration["friendlyPath"];
                    }
                }
            }
            
        }

        private void InitWitsFromTFS(TfsJSON tfsReader, string dbConnection)
        {
            var jsonPayload = tfsReader.TfsCategories(m_projectName);
            var root = (Dictionary<string, object>)jsonPayload;
            if (root == null)
            {
                Console.WriteLine("This team has not been configured to use the Agile Tools.");
                return;
            }
            object[] reqs = (object[])((Dictionary<string,object>)root["Microsoft.RequirementCategory"])["workItemTypeNames"];
            foreach (string name in reqs)
            {
                m_reqWits.Add(name);
            }
            object[] tasks = (object[])((Dictionary<string, object>)root["Microsoft.TaskCategory"])["workItemTypeNames"];
            foreach (string name in tasks)
            {
                m_taskWits.Add(name);
            }

            using (SqlConnection connection = new SqlConnection(dbConnection))
            {
                connection.Open();
                string sqlQuery = "SELECT DISTINCT " +
                                    "    dos.CategoryRefName " +
                                    "    ,uno.[StateType] " +
                                    "    ,uno.[StateValue] " +
                                    "FROM [Tfs_DevDiv].[dbo].[tbl_ProjectConfigurationWorkItemStates] AS uno " +
                                    "LEFT JOIN [Tfs_DevDiv].[dbo].[tbl_ProjectConfigurationWorkItemTypes] AS dos " +
                                    "ON  (uno.WorkItemType = dos.WorkItemType) " +
                                    "WHERE uno.[ProjectId] ='" + m_projectId + "'";
                SqlCommand getPaths = new SqlCommand(sqlQuery, connection);
                using (SqlDataReader reader = getPaths.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        if (reader[0] != null)
                        {
                            string category = reader[0].ToString();
                            string type = reader[1].ToString();
                            string state = reader[2].ToString();

                            if (type == "3")
                            {
                                switch (category)
                                {
                                    case "Microsoft.TaskCategory":
                                        m_taskFinishedState = state;
                                        break;
                                    case "Microsoft.RequirementCategory":
                                        m_reqFinishedState = state;
                                        break;
                                }
                            }
                        }
                    }
                }
                connection.Close();
            }
            
        }

        private void InitAreaPathsfromDB(string dbConnection)
        {
            using (SqlConnection connection = new SqlConnection(dbConnection))
            {
                connection.Open();
                SqlCommand getPaths = new SqlCommand("SELECT [TeamFieldValue],[IncludeChildren],[Order] FROM [Tfs_DevDiv].[dbo].[tbl_TeamConfigurationTeamFields] WHERE [TeamId] ='" + m_id + "'", connection);
                using (SqlDataReader reader = getPaths.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        if (reader[0] != null)
                        {
                            m_areaPaths.Add(reader[0].ToString(), Convert.ToBoolean(reader[1].ToString()));
                        }
                    }
                }
                connection.Close();
            }
        }

        public void rollWorkForward()
        {
            if (m_previousIteration != null && m_currentIteration != null)
            {
                using (var tpc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(m_collection)))
                {
                    WorkItemStore witStore = tpc.GetService<WorkItemStore>();
                    try
                    {
                        List<string> areaFilters = new List<string>();
                        string areaFilter;
                        foreach (KeyValuePair<string, Boolean> path in m_areaPaths)
                        {
                            areaFilter = "[System.AreaPath]";
                            if (path.Value)
                                areaFilter += " Under ";
                            else
                            {
                                areaFilter += "=";
                            }
                            areaFilter += "'" + path.Key + "'";
                            areaFilters.Add(areaFilter);
                        }

                        string wiqlQuery = "Select ID, [System.AreaPath], [System.IterationPath], State, [System.WorkItemType] from WorkItemLinks" +
                                                 " WHERE " +
                                                 "(Source.[System.WorkItemType] = '" +
                                                 string.Join("' or Source.[System.WorkItemType] = '", m_reqWits.ToArray()) +
                                                 "') AND (" +
                                                 " Source.[System.IterationPath] = '" + m_previousIteration + "'" +
                                                 ") AND (" +
                                                 " Source.[State] <> '" + m_reqFinishedState + "' AND Source.[State] <> 'Removed'" +
                                                 ") AND (Source." +
                                                 string.Join(" or Source.", areaFilters.ToArray()) +
                                                 ") AND ([System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward'" +
                                                 ") AND (" +
                                                 " Target.[System.WorkItemType] = '" +
                                                 string.Join("' or Target.[System.WorkItemType] = '", m_taskWits.ToArray()) +
                                                 "') AND (Target." +
                                                 string.Join(" or Target.", areaFilters.ToArray()) +
                                                 ") AND (" +
                                                 " Target.[System.IterationPath] = '" + m_previousIteration + "'" +
                                                 ") AND (" +
                                                 " Target.[State] <> '" + m_taskFinishedState + "' AND Target.[State] <> 'Removed'" +
                                                 ") order by ID" +
                                                 " mode (Recursive)";

                        BatchReadParameterCollection flattenedParameters = new BatchReadParameterCollection();
                        WorkItemCollection workItemsToUpdate;

                        Query newQuery = new Query(witStore, wiqlQuery, new Hashtable { { "project", m_projectName } });
                        var wbdResults = newQuery.RunLinkQuery();
                        if (wbdResults.Count() > 0)
                        {
                            foreach (WorkItemLinkInfo witInfo in wbdResults)
                            {
                                flattenedParameters.Add(new BatchReadParameter(witInfo.TargetId));
                            }
                        }
                        if (flattenedParameters.Count == 0)
                        {
                            Console.WriteLine("There are no work items to update");
                            return;
                        }

                        workItemsToUpdate = witStore.Query(flattenedParameters, "select [System.Id], [System.Title] from workitems");

                        Console.WriteLine("Found " + workItemsToUpdate.Count.ToString() + " work items to update.");
                        Console.WriteLine("Work items will be moved from '" + m_previousIteration + "' to '" + m_currentIteration + "'");
                        Console.WriteLine("Updating work items, please wait...");
                        foreach (WorkItem workItem in workItemsToUpdate)
                        {

                            //This is debug code to make sure I'm getting the right wits.
                            foreach (FieldDefinition fieldDefinition in workItemsToUpdate.Query.DisplayFieldList)
                            {
                                //Console.Write("{0}\t", workItem[fieldDefinition.Name]);
                            }
                            //Console.WriteLine(String.Empty);

                            //Open each work item and save to new iteration
                            workItem.Open();
                            workItem.IterationPath = m_currentIteration;

                            // make sure all the fields are valid before saving
                            if (workItem.Fields.Cast<Field>().Where(field => !field.IsValid).Any())
                            {
                                throw new ApplicationException("There are invalid field values");
                            }

                            // save the edited work item
                            workItem.Save();

                        }

                        Console.WriteLine("Updated all work items");


                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("Error: {0}", e.Message);
                    }
                }
            }
            else
            {
                Console.WriteLine("Team has no Agile Settings, skipping");
            }
        }
    }
}
